[sources/path] Add gitignore-like pattern matching and warn on mismatches
authorBehnam Esfahbod <behnam@zwnj.org>
Fri, 7 Jul 2017 18:52:03 +0000 (12:52 -0600)
committerBehnam Esfahbod <behnam@zwnj.org>
Tue, 18 Jul 2017 06:14:14 +0000 (00:14 -0600)
Add gitignore-like pattern matching logic to `list_files()` and throw
warnings for paths getting different inclusion/exclusion results from
the old and the new methods.

Migration Tracking: <https://github.com/rust-lang/cargo/issues/4268>

Cargo.lock
Cargo.toml
src/cargo/lib.rs
src/cargo/sources/path.rs
src/cargo/util/errors.rs
tests/package.rs

index 306ede34e43712cc1afb79797f30c4720ac1803a..6205be6e6c25add3f37d02ee2b12040f9ec8c2a2 100644 (file)
@@ -20,6 +20,7 @@ dependencies = [
  "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "hamcrest 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "jobserver 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -251,6 +252,11 @@ dependencies = [
  "miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "fnv"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "foreign-types"
 version = "0.2.0"
@@ -300,6 +306,18 @@ name = "glob"
 version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "globset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "hamcrest"
 version = "0.1.1"
@@ -324,6 +342,21 @@ dependencies = [
  "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "ignore"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "globset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "itoa"
 version = "0.3.1"
@@ -630,6 +663,15 @@ name = "rustc-serialize"
 version = "0.3.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "same-file"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "scoped-tls"
 version = "0.1.0"
@@ -847,6 +889,16 @@ name = "void"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "walkdir"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "winapi"
 version = "0.2.8"
@@ -896,15 +948,18 @@ dependencies = [
 "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8"
 "checksum filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5363ab8e4139b8568a6237db5248646e5a8a2f89bd5ccb02092182b11fd3e922"
 "checksum flate2 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "36df0166e856739905cd3d7e0b210fe818592211a008862599845e012d8d304c"
+"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
 "checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d"
 "checksum fs2 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ab76cfd2aaa59b7bf6688ad9ba15bbae64bff97f04ea02144cfd3443e5c2866"
 "checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a"
 "checksum git2 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa01936ac96555c083c0e8553f672616274408d9d3fc5b8696603fbf63ff43ee"
 "checksum git2-curl 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "68676bc784bf0bef83278898929bf64a251e87c0340723d0b93fa096c9c5bf8e"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
+"checksum globset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "feeb1b6840809ef5efcf7a4a990bc4e1b7ee3df8cf9e2379a75aeb2ba42ac9c3"
 "checksum hamcrest 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bf088f042a467089e9baa4972f57f9247e42a0cc549ba264c7a04fbb8ecb89d4"
 "checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa"
 "checksum idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2233d4940b1f19f0418c158509cd7396b8d70a5db5705ce410914dc8fa603b37"
+"checksum ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3fcaf2365eb14b28ec7603c98c06cc531f19de9eb283d89a3dff8417c8c99f5"
 "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c"
 "checksum jobserver 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "443ae8bc0af6c106e6e8b77e04684faecc1a5ce94e058f4c2b0a037b0ea1b133"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
@@ -942,6 +997,7 @@ dependencies = [
 "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
 "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95"
 "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
+"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
 "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d"
 "checksum semver 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdd61b85a0fa777f7fb7c454b9189b2941b110d1385ce84d7f76efdf1606a85"
 "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
@@ -971,6 +1027,7 @@ dependencies = [
 "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
 "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
 "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
 "checksum wincolor 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a39ee4464208f6430992ff20154216ab2357772ac871d994c51628d60e58b8b0"
index d7ea2ccfbc82ba9a594f3179b50f2348c3a86dbd..263f061de3ce9a34e77d7bae795a84bdfee1f521 100644 (file)
@@ -31,6 +31,7 @@ git2 = "0.6"
 git2-curl = "0.7"
 glob = "0.2"
 hex = "0.2"
+ignore = "^0.2.2"
 jobserver = "0.1.6"
 libc = "0.2"
 libgit2-sys = "0.6"
index 5a1c3de149c9397e2741c24f8091a8f374e246c4..1b96813433bbe0fc6354c82483f306d5ae56c856 100755 (executable)
@@ -19,6 +19,7 @@ extern crate fs2;
 extern crate git2;
 extern crate glob;
 extern crate hex;
+extern crate ignore;
 extern crate jobserver;
 extern crate libc;
 extern crate libgit2_sys;
index 7a21713f49ace85f3bd13cd61677b8b03c5e1772..317133f0b70d4f433a3846c0e479adfa083def53 100644 (file)
@@ -5,6 +5,8 @@ use std::path::{Path, PathBuf};
 use filetime::FileTime;
 use git2;
 use glob::Pattern;
+use ignore::Match;
+use ignore::gitignore::GitignoreBuilder;
 
 use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
 use ops;
@@ -85,42 +87,163 @@ impl<'cfg> PathSource<'cfg> {
     /// The basic assumption of this method is that all files in the directory
     /// are relevant for building this package, but it also contains logic to
     /// use other methods like .gitignore to filter the list of files.
+    ///
+    /// ## Pattern matching strategy
+    ///
+    /// Migrating from a glob-like pattern matching (using `glob` crate) to a
+    /// gitignore-like pattern matching (using `ignore` crate). The migration
+    /// stages are:
+    ///
+    /// 1) Only warn users about the future change iff their matching rules are
+    ///    affected.  (CURRENT STAGE)
+    ///
+    /// 2) Switch to the new strategy and upate documents. Still keep warning
+    ///    affected users.
+    ///
+    /// 3) Drop the old strategy and no mor warnings.
+    ///
+    /// See <https://github.com/rust-lang/cargo/issues/4268> for more info.
     pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathBuf>> {
         let root = pkg.root();
+        let no_include_option = pkg.manifest().include().is_empty();
+
+        // glob-like matching rules
 
-        let parse = |p: &String| {
+        let glob_parse = |p: &String| {
             Pattern::new(p).map_err(|e| {
-                CargoError::from(format!("could not parse pattern `{}`: {}", p, e))
+                CargoError::from(format!("could not parse glob pattern `{}`: {}", p, e))
             })
         };
 
-        let exclude = pkg.manifest()
-                         .exclude()
-                         .iter()
-                         .map(|p| parse(p))
-                         .collect::<Result<Vec<_>, _>>()?;
-
-        let include = pkg.manifest()
-                         .include()
-                         .iter()
-                         .map(|p| parse(p))
-                         .collect::<Result<Vec<_>, _>>()?;
-
-        let mut filter = |p: &Path| {
-            let relative_path = util::without_prefix(p, root).unwrap();
-            include.iter().any(|p| p.matches_path(relative_path)) || {
-                include.is_empty() &&
-                 !exclude.iter().any(|p| p.matches_path(relative_path))
+        let glob_exclude = pkg.manifest()
+            .exclude()
+            .iter()
+            .map(|p| glob_parse(p))
+            .collect::<Result<Vec<_>, _>>()?;
+
+        let glob_include = pkg.manifest()
+            .include()
+            .iter()
+            .map(|p| glob_parse(p))
+            .collect::<Result<Vec<_>, _>>()?;
+
+        let glob_should_package = |relative_path: &Path| -> bool {
+            // include and exclude options are mutually exclusive.
+            if no_include_option {
+                !glob_exclude.iter().any(|pattern| {
+                    pattern.matches_path(relative_path)
+                })
+            } else {
+                glob_include.iter().any(|pattern| {
+                    pattern.matches_path(relative_path)
+                })
+            }
+        };
+
+        // ignore-like matching rules
+
+        let mut exclude_builder = GitignoreBuilder::new(root);
+        for rule in pkg.manifest().exclude() {
+            exclude_builder.add_line(None, rule)?;
+        }
+        let ignore_exclude = exclude_builder.build()?;
+
+        let mut include_builder = GitignoreBuilder::new(root);
+        for rule in pkg.manifest().include() {
+            include_builder.add_line(None, rule)?;
+        }
+        let ignore_include = include_builder.build()?;
+
+        let ignore_should_package = |relative_path: &Path| -> CargoResult<bool> {
+            // include and exclude options are mutually exclusive.
+            if no_include_option {
+                match ignore_exclude.matched_path_or_any_parents(
+                    relative_path,
+                    /* is_dir */ false,
+                ) {
+                    Match::None => Ok(true),
+                    Match::Ignore(_) => Ok(false),
+                    Match::Whitelist(pattern) => Err(CargoError::from(format!(
+                        "exclude rules cannot start with `!`: {}",
+                        pattern.original()
+                    ))),
+                }
+            } else {
+                match ignore_include.matched_path_or_any_parents(
+                    relative_path,
+                    /* is_dir */ false,
+                ) {
+                    Match::None => Ok(false),
+                    Match::Ignore(_) => Ok(true),
+                    Match::Whitelist(pattern) => Err(CargoError::from(format!(
+                        "include rules cannot start with `!`: {}",
+                        pattern.original()
+                    ))),
+                }
+            }
+        };
+
+        // matching to paths
+
+        let mut filter = |path: &Path| -> CargoResult<bool> {
+            let relative_path = util::without_prefix(path, root).unwrap();
+            let glob_should_package = glob_should_package(relative_path);
+            let ignore_should_package = ignore_should_package(relative_path)?;
+
+            if glob_should_package != ignore_should_package {
+                if glob_should_package {
+                    if no_include_option {
+                        self.config
+                            .shell()
+                            .warn(format!(
+                                "Pattern matching for Cargo's include/exclude fields is changing and \
+                                file `{}` WILL be excluded in the next Cargo version.\n\
+                                See https://github.com/rust-lang/cargo/issues/4268 for more info",
+                                relative_path.display()
+                            ))?;
+                    } else {
+                        self.config
+                            .shell()
+                            .warn(format!(
+                                "Pattern matching for Cargo's include/exclude fields is changing and \
+                                file `{}` WILL NOT be included in the next Cargo version.\n\
+                                See https://github.com/rust-lang/cargo/issues/4268 for more info",
+                                relative_path.display()
+                            ))?;
+                    }
+                } else {
+                    if no_include_option {
+                        self.config
+                            .shell()
+                            .warn(format!(
+                                "Pattern matching for Cargo's include/exclude fields is changing and \
+                                file `{}` WILL NOT be excluded in the next Cargo version.\n\
+                                See https://github.com/rust-lang/cargo/issues/4268 for more info",
+                                relative_path.display()
+                            ))?;
+                    } else {
+                        self.config
+                            .shell()
+                            .warn(format!(
+                                "Pattern matching for Cargo's include/exclude fields is changing and \
+                                file `{}` WILL be included in the next Cargo version.\n\
+                                See https://github.com/rust-lang/cargo/issues/4268 for more info",
+                                relative_path.display()
+                            ))?;
+                    }
+                }
             }
+
+            // Update to ignore_should_package for Stage 2
+            Ok(glob_should_package)
         };
 
         // attempt git-prepopulate only if no `include` (rust-lang/cargo#4135)
-        if include.is_empty() {
+        if no_include_option {
             if let Some(result) = self.discover_git_and_list_files(pkg, root, &mut filter) {
                 return result;
             }
         }
-
         self.list_files_walk(pkg, &mut filter)
     }
 
@@ -129,7 +252,7 @@ impl<'cfg> PathSource<'cfg> {
     fn discover_git_and_list_files(&self,
                                    pkg: &Package,
                                    root: &Path,
-                                   filter: &mut FnMut(&Path) -> bool)
+                                   filter: &mut FnMut(&Path) -> CargoResult<bool>)
                                    -> Option<CargoResult<Vec<PathBuf>>> {
         // If this package is in a git repository, then we really do want to
         // query the git repository as it takes into account items such as
@@ -170,7 +293,7 @@ impl<'cfg> PathSource<'cfg> {
     }
 
     fn list_files_git(&self, pkg: &Package, repo: git2::Repository,
-                      filter: &mut FnMut(&Path) -> bool)
+                      filter: &mut FnMut(&Path) -> CargoResult<bool>)
                       -> CargoResult<Vec<PathBuf>> {
         warn!("list_files_git {}", pkg.package_id());
         let index = repo.index()?;
@@ -202,7 +325,7 @@ impl<'cfg> PathSource<'cfg> {
         let untracked = statuses.iter().filter_map(|entry| {
             match entry.status() {
                 git2::STATUS_WT_NEW => Some((join(root, entry.path_bytes()), None)),
-                _ => None
+                _ => None,
             }
         });
 
@@ -262,11 +385,10 @@ impl<'cfg> PathSource<'cfg> {
                         ret.extend(files.into_iter());
                     }
                     Err(..) => {
-                        PathSource::walk(&file_path, &mut ret, false,
-                                              filter)?;
+                        PathSource::walk(&file_path, &mut ret, false, filter)?;
                     }
                 }
-            } else if (*filter)(&file_path) {
+            } else if (*filter)(&file_path)? {
                 // We found a file!
                 warn!("  found {}", file_path.display());
                 ret.push(file_path);
@@ -291,7 +413,7 @@ impl<'cfg> PathSource<'cfg> {
         }
     }
 
-    fn list_files_walk(&self, pkg: &Package, filter: &mut FnMut(&Path) -> bool)
+    fn list_files_walk(&self, pkg: &Package, filter: &mut FnMut(&Path) -> CargoResult<bool>)
                        -> CargoResult<Vec<PathBuf>> {
         let mut ret = Vec::new();
         PathSource::walk(pkg.root(), &mut ret, true, filter)?;
@@ -299,10 +421,11 @@ impl<'cfg> PathSource<'cfg> {
     }
 
     fn walk(path: &Path, ret: &mut Vec<PathBuf>,
-            is_root: bool, filter: &mut FnMut(&Path) -> bool) -> CargoResult<()>
+            is_root: bool, filter: &mut FnMut(&Path) -> CargoResult<bool>)
+            -> CargoResult<()>
     {
         if !fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false) {
-            if (*filter)(path) {
+            if (*filter)(path)? {
                 ret.push(path.to_path_buf());
             }
             return Ok(())
@@ -311,9 +434,18 @@ impl<'cfg> PathSource<'cfg> {
         if !is_root && fs::metadata(&path.join("Cargo.toml")).is_ok() {
             return Ok(())
         }
-        for dir in fs::read_dir(path)? {
-            let dir = dir?.path();
-            let name = dir.file_name().and_then(|s| s.to_str());
+
+        // For package integration tests, we need to sort the paths in a deterministic order to
+        // be able to match stdout warnings in the same order.
+        //
+        // TODO: Drop collect and sort after transition period and dropping wraning tests.
+        // See <https://github.com/rust-lang/cargo/issues/4268>
+        // and <https://github.com/rust-lang/cargo/pull/4270>
+        let mut entries: Vec<fs::DirEntry> = fs::read_dir(path)?.map(|e| e.unwrap()).collect();
+        entries.sort_by(|a, b| a.path().as_os_str().cmp(b.path().as_os_str()));
+        for entry in entries {
+            let path = entry.path();
+            let name = path.file_name().and_then(|s| s.to_str());
             // Skip dotfile directories
             if name.map(|s| s.starts_with('.')) == Some(true) {
                 continue
@@ -324,7 +456,7 @@ impl<'cfg> PathSource<'cfg> {
                     _ => {}
                 }
             }
-            PathSource::walk(&dir, ret, false, filter)?;
+            PathSource::walk(&path, ret, false, filter)?;
         }
         Ok(())
     }
index dd748a459b7df07e1ca9b0e1cdbdcead5380029d..0f4cd4cb4c23fa9d897ea40efc015f8e8d1485f1 100644 (file)
@@ -14,6 +14,7 @@ use semver;
 use serde_json;
 use toml;
 use registry;
+use ignore;
 
 error_chain! {
     types {
@@ -27,6 +28,7 @@ error_chain! {
     foreign_links {
         ParseSemver(semver::ReqParseError);
         Semver(semver::SemVerError);
+        Ignore(ignore::Error);
         Io(io::Error);
         SerdeJson(serde_json::Error);
         TomlSer(toml::ser::Error);
@@ -74,6 +76,7 @@ impl CargoError {
             &CargoErrorKind::CrateRegistry(_) => true,
             &CargoErrorKind::ParseSemver(_) |
             &CargoErrorKind::Semver(_) |
+            &CargoErrorKind::Ignore(_) |
             &CargoErrorKind::Io(_) |
             &CargoErrorKind::SerdeJson(_) |
             &CargoErrorKind::ParseInt(_) |
index d54fdc2375a5e9dc3e95d2965347dbec4f5cdca4..1a01ac0cdb890e04e618d764b6f4b7186ba87bda 100644 (file)
@@ -251,21 +251,132 @@ fn exclude() {
             name = "foo"
             version = "0.0.1"
             authors = []
-            exclude = ["*.txt"]
+            exclude = [
+                "*.txt",
+                # file in root
+                "file_root_1",       # NO_CHANGE (ignored)
+                "/file_root_2",      # CHANGING (packaged -> ignored)
+                "file_root_3/",      # NO_CHANGE (packaged)
+                "file_root_4/*",     # NO_CHANGE (packaged)
+                "file_root_5/**",    # NO_CHANGE (packaged)
+                # file in sub-dir
+                "file_deep_1",       # CHANGING (packaged -> ignored)
+                "/file_deep_2",      # NO_CHANGE (packaged)
+                "file_deep_3/",      # NO_CHANGE (packaged)
+                "file_deep_4/*",     # NO_CHANGE (packaged)
+                "file_deep_5/**",    # NO_CHANGE (packaged)
+                # dir in root
+                "dir_root_1",        # CHANGING (packaged -> ignored)
+                "/dir_root_2",       # CHANGING (packaged -> ignored)
+                "dir_root_3/",       # CHANGING (packaged -> ignored)
+                "dir_root_4/*",      # NO_CHANGE (ignored)
+                "dir_root_5/**",     # NO_CHANGE (ignored)
+                # dir in sub-dir
+                "dir_deep_1",        # CHANGING (packaged -> ignored)
+                "/dir_deep_2",       # NO_CHANGE
+                "dir_deep_3/",       # CHANGING (packaged -> ignored)
+                "dir_deep_4/*",      # CHANGING (packaged -> ignored)
+                "dir_deep_5/**",     # CHANGING (packaged -> ignored)
+            ]
         "#)
         .file("src/main.rs", r#"
             fn main() { println!("hello"); }
         "#)
         .file("bar.txt", "")
-        .file("src/bar.txt", "");
+        .file("src/bar.txt", "")
+        // file in root
+        .file("file_root_1", "")
+        .file("file_root_2", "")
+        .file("file_root_3", "")
+        .file("file_root_4", "")
+        .file("file_root_5", "")
+        // file in sub-dir
+        .file("some_dir/file_deep_1", "")
+        .file("some_dir/file_deep_2", "")
+        .file("some_dir/file_deep_3", "")
+        .file("some_dir/file_deep_4", "")
+        .file("some_dir/file_deep_5", "")
+        // dir in root
+        .file("dir_root_1/some_dir/file", "")
+        .file("dir_root_2/some_dir/file", "")
+        .file("dir_root_3/some_dir/file", "")
+        .file("dir_root_4/some_dir/file", "")
+        .file("dir_root_5/some_dir/file", "")
+        // dir in sub-dir
+        .file("some_dir/dir_deep_1/some_dir/file", "")
+        .file("some_dir/dir_deep_2/some_dir/file", "")
+        .file("some_dir/dir_deep_3/some_dir/file", "")
+        .file("some_dir/dir_deep_4/some_dir/file", "")
+        .file("some_dir/dir_deep_5/some_dir/file", "")
+        ;
 
     assert_that(p.cargo_process("package").arg("--no-verify").arg("-v"),
-                execs().with_status(0).with_stderr("\
+                execs().with_status(0).with_stdout("").with_stderr("\
 [WARNING] manifest has no description[..]
 See http://doc.crates.io/manifest.html#package-metadata for more info.
 [PACKAGING] foo v0.0.1 ([..])
+[WARNING] [..] file `dir_root_1[/]some_dir[/]file` WILL be excluded [..]
+See [..]
+[WARNING] [..] file `dir_root_2[/]some_dir[/]file` WILL be excluded [..]
+See [..]
+[WARNING] [..] file `dir_root_3[/]some_dir[/]file` WILL be excluded [..]
+See [..]
+[WARNING] [..] file `file_root_2` WILL be excluded [..]
+See [..]
+[WARNING] [..] file `some_dir[/]dir_deep_1[/]some_dir[/]file` WILL be excluded [..]
+See [..]
+[WARNING] [..] file `some_dir[/]dir_deep_3[/]some_dir[/]file` WILL be excluded [..]
+See [..]
+[WARNING] [..] file `some_dir[/]dir_deep_4[/]some_dir[/]file` WILL be excluded [..]
+See [..]
+[WARNING] [..] file `some_dir[/]dir_deep_5[/]some_dir[/]file` WILL be excluded [..]
+See [..]
+[WARNING] [..] file `some_dir[/]file_deep_1` WILL be excluded [..]
+See [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
 [ARCHIVING] [..]
 [ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+[ARCHIVING] [..]
+"));
+
+    assert_that(&p.root().join("target/package/foo-0.0.1.crate"), existing_file());
+
+    assert_that(p.cargo("package").arg("-l"),
+                execs().with_status(0).with_stdout("\
+Cargo.toml
+dir_root_1[/]some_dir[/]file
+dir_root_2[/]some_dir[/]file
+dir_root_3[/]some_dir[/]file
+file_root_2
+file_root_3
+file_root_4
+file_root_5
+some_dir[/]dir_deep_1[/]some_dir[/]file
+some_dir[/]dir_deep_2[/]some_dir[/]file
+some_dir[/]dir_deep_3[/]some_dir[/]file
+some_dir[/]dir_deep_4[/]some_dir[/]file
+some_dir[/]dir_deep_5[/]some_dir[/]file
+some_dir[/]file_deep_1
+some_dir[/]file_deep_2
+some_dir[/]file_deep_3
+some_dir[/]file_deep_4
+some_dir[/]file_deep_5
+src[/]main.rs
 "));
 }